package com.kamistoat.meimeistore.cart.service.impl;

import com.alibaba.fastjson.JSON;
import com.kamistoat.common.To.SkuCartInfoTo;
import com.kamistoat.common.constant.CartConstant;
import com.kamistoat.common.utils.R;
import com.kamistoat.meimeistore.cart.feign.ProductFeignService;
import com.kamistoat.meimeistore.cart.feign.WareFeignService;
import com.kamistoat.meimeistore.cart.interceptor.CartInterceptor;
import com.kamistoat.meimeistore.cart.service.CartService;
import com.kamistoat.meimeistore.cart.vo.CartItemVo;
import com.kamistoat.meimeistore.cart.vo.CartVo;
import com.kamistoat.meimeistore.cart.vo.UserInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

@Slf4j
@Service
public class CartServiceImpl implements CartService {
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    ProductFeignService productFeignService;
    @Autowired
    WareFeignService wareFeignService;
    @Autowired
    ThreadPoolExecutor threadPoolExecutor;


    /**
     * 添加到购物车
     *
     * @param skuId 商品skuId
     * @param num   购买数量
     * @return
     */
    @Override
    public CartItemVo addToCart(Long skuId, Integer num, Boolean hasStock) {
        BoundHashOperations<String, Object, Object> cartKeyRedis = getCartKeyRedis();
        // 如果购物车中已经有这个商品了，直接增加数量
        String item = (String) cartKeyRedis.get(skuId.toString());// 直接在当前购物车里用键名去查
        if (!StringUtils.isEmpty(item)) {
            // 购物车中已经有这个商品
            // 先将原始商品信息提取出来
            CartItemVo cartItemVo = JSON.parseObject(item, CartItemVo.class);
            // 将商品原来的数量加上新的数量后再放回reids中
            cartItemVo.setCount(cartItemVo.getCount() + num);
            cartKeyRedis.put(skuId.toString(), JSON.toJSONString(cartItemVo));
            // 当然在页面上展示的还是这一次的num
            cartItemVo.setCount(num);
            return cartItemVo;
        } else {
            // 向购物车中添加新商品
            // 1 根据skuId远程查询商品信息
            // 异步的
            CartItemVo cartItemVo = new CartItemVo();
            CompletableFuture<Void> getSkuCartInfo = CompletableFuture.runAsync(() -> {
                SkuCartInfoTo skuCartInfoTo = productFeignService.getSkuCartInfo(skuId);
                // 2 将商品添加到购物车
                cartItemVo.setHasStock(hasStock);
                cartItemVo.setCheck(hasStock);
                cartItemVo.setCount(num);
                cartItemVo.setImage(skuCartInfoTo.getSkuDefaultImg());
                cartItemVo.setTitle(skuCartInfoTo.getSkuTitle());
                cartItemVo.setSkuId(skuId);
                cartItemVo.setPrice(skuCartInfoTo.getPrice());
                cartItemVo.setBrandId(skuCartInfoTo.getBrandId());
                cartItemVo.setBrandName(skuCartInfoTo.getBrandName());
            }, threadPoolExecutor);

            // 3、根据skuId获取String形式的销售属性
            // 异步的
            CompletableFuture<Void> getStringAttr = CompletableFuture.runAsync(() -> {
                List<String> stringAttr = productFeignService.getStringAttr(skuId);
                cartItemVo.setSkuAttr(stringAttr);
            }, threadPoolExecutor);

            // 等待异步查询全部完成
            try {
                CompletableFuture.allOf(getSkuCartInfo, getStringAttr).get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }

            // 4、保存到redis中
            // 因为已经绑定了是对HAX操作，所以这里也不用写opsForHax了，不然每次都得写。
            cartKeyRedis.put(skuId.toString(), JSON.toJSONString(cartItemVo));

            return cartItemVo;
        }
    }


    /**
     * 查询刚刚成功添加的商品
     *
     * @param skuId
     * @param num
     * @return
     */
    @Override
    public CartItemVo getCartItem(Long skuId, Integer num) {
        BoundHashOperations<String, Object, Object> cartKeyRedis = getCartKeyRedis();
        String item = (String) cartKeyRedis.get(skuId.toString());// 直接在当前购物车里用键名去查
        // 就不用判断是否为空了
        // 将刚刚添加的商品信息提取出来
        CartItemVo cartItemVo = JSON.parseObject(item, CartItemVo.class);
        // 将商品的数量更改为刚刚添加的数据
        cartItemVo.setCount(num);
        // 返回给页面展示
        return cartItemVo;

    }


    /**
     * 获得购物车全部内容
     *
     * @return
     */
    @Override
    public CartVo getCart() {
        CartVo cartVo = new CartVo();
        UserInfoVo userInfoVo = CartInterceptor.threadLocal.get();

        // 绑定登录/没登录的redis
        BoundHashOperations<String, Object, Object> cartKeyRedis = getCartKeyRedis();

        // 将这个绑定的redis的key与session/user-key进行比较，来确定是登录还是未登录
        if (cartKeyRedis.getKey().equals(CartConstant.CART_PREFIX + userInfoVo.getUserKey())) {
            // 如果是未登录状态，即redis的key是user-key。直接展示临时购物车内容即可
            // 直接从绑定的redis中把所有的临时购物车商品提取出来
            List<CartItemVo> OFFLINE_cartItemVoList = getCartItemVoList(userInfoVo.getUserKey());
            cartVo.setCartItemVos(OFFLINE_cartItemVoList);
        } else {
            // 如果是登录状态，则要将离线购物车中的内容合并到在线购物车再进行展示
            // 先把离线购物车商品信息获取来进行空值判断
            List<CartItemVo> OFFLINE_cartItemVoList = getCartItemVoList(userInfoVo.getUserKey());
            if (OFFLINE_cartItemVoList != null && OFFLINE_cartItemVoList.size() > 0) {
                // 离线购物车不为空，则进行合并
                // 因为现在一定是在已经登录的状态，所以直接调用添加购物车方法把这些方法再添一遍即可。
                for (CartItemVo cartItemVo : OFFLINE_cartItemVoList) {
                    addToCart(cartItemVo.getSkuId(), cartItemVo.getCount(), cartItemVo.getHasStock());
                }
                // 合并完成后清除离线购物车数据
                clearCart(userInfoVo.getUserKey());
            }
            // 再把在线购物车商品信息获取来，此时就是已经合并了离线购物车商品了
            List<CartItemVo> ONLINE_cartItemVoList = getCartItemVoList(userInfoVo.getUserId().toString());
            cartVo.setCartItemVos(ONLINE_cartItemVoList);
        }

        if (cartVo.getCartItemVos() != null && cartVo.getCartItemVos().size() > 0) {
            // 每次进入购物车需要查询一遍库存
            List<Long> skuIdList = cartVo.getCartItemVos().stream().map(cartItemVo -> {
                return cartItemVo.getSkuId();
            }).collect(Collectors.toList());
            R hasStock = wareFeignService.hasStock(skuIdList);
            Map<String, Boolean> skuHasStock = (Map<String, Boolean>) hasStock.get("data");
            for (CartItemVo cartItemVo : cartVo.getCartItemVos()) {
                cartItemVo.setHasStock(skuHasStock.get(cartItemVo.getSkuId().toString()));
            }
        }
        return cartVo;
    }


    /**
     * 根据是否登录，绑定离线购物车/在线购物车
     * 在addToCart()方法中被调用
     *
     * @return 对redis中指定哈希表绑定的redis操作类
     */
    @Override
    public BoundHashOperations<String, Object, Object> getCartKeyRedis() {
        // 因为是同一线程，所以直接可以从ThreadLocal中获取用户信息
        UserInfoVo userInfoVo = CartInterceptor.threadLocal.get();

        // 判断是离线购物车还是在线购物车
        String cartKey = "";
        if (userInfoVo.getUserId() != null) {
            // 在线购物车
            cartKey = CartConstant.CART_PREFIX + userInfoVo.getUserId();
        } else if (!StringUtils.isEmpty(userInfoVo.getUserKey())) {
            // 离线购物车
            cartKey = CartConstant.CART_PREFIX + userInfoVo.getUserKey();
        }

        // 判断购物车中是否已经存在相同的商品了
        // 绑定一个HAX操作。接下来cartKeyRedis的操作对象是键名为cartKey的redis哈希表。这是一个绑定操作
        // 键名为cartKey的redis哈希表也就是购物车模型的第一层Map
        BoundHashOperations<String, Object, Object> cartKeyRedis = stringRedisTemplate.boundHashOps(cartKey);
        return cartKeyRedis;
    }


    /**
     * 勾选购物车商品项
     *
     * @param skuId
     * @param check
     */
    @Override
    public void checkItem(Long skuId, Integer check) {
        // 首先把绑定的redis购物车获取来。此时无需判断是离线购物车还是在线购物车。
        BoundHashOperations<String, Object, Object> cartKeyRedis = getCartKeyRedis();
        // 从当前绑定redis购物车中以skuId为键值将对应的商品项拿出来
        CartItemVo cartItemVo = JSON.parseObject((String) cartKeyRedis.get(skuId.toString()), CartItemVo.class);
        // 更改商品项的check状态
        if (cartItemVo != null) {
            cartItemVo.setCheck(check == 1);
        }
        // 还得把修改完的购物商品信息保存回redis中，依然是值和value都得转成String才行
        String cartItemVoJSON = JSON.toJSONString(cartItemVo);
        cartKeyRedis.put(skuId.toString(), cartItemVoJSON);
    }

    /**
     * 修改购物商品项数量
     *
     * @param skuId
     * @param num
     */
    @Override
    public void countItem(Long skuId, Integer num) {
        // 首先把绑定的redis购物车获取来。此时无需判断是离线购物车还是在线购物车。
        BoundHashOperations<String, Object, Object> cartKeyRedis = getCartKeyRedis();
        // 从当前绑定redis购物车中以skuId为键值将对应的商品项拿出来
        CartItemVo cartItemVo = JSON.parseObject((String) cartKeyRedis.get(skuId.toString()), CartItemVo.class);
        // 更改商品项数量
        if (cartItemVo != null) {
            cartItemVo.setCount(num);
        }
        // 还得把修改完的购物商品信息保存回redis中，依然是值和value都得转成String才行
        String cartItemVoJSON = JSON.toJSONString(cartItemVo);
        cartKeyRedis.put(skuId.toString(), cartItemVoJSON);
    }

    /**
     * 删除某个商品项
     *
     * @param skuId
     */
    @Override
    public void deleteItem(Long skuId) {
        // 首先把绑定的redis购物车获取来。此时无需判断是离线购物车还是在线购物车。
        BoundHashOperations<String, Object, Object> cartKeyRedis = getCartKeyRedis();
        cartKeyRedis.delete(skuId.toString());
    }

    /**
     * 获取当前用户购物车中所有被选中的商品
     * 并不需要传入用户Id，因为购物车服务同样有拦截器的ThreadLocal，直接从里面那就可以了
     */
    @Override
    public List<CartItemVo> getOrderCartChecked() {
        // 从拦截器中把用户信息拿出来。只有在登录状态下才和购物车结算联动
        UserInfoVo userInfoVo = CartInterceptor.threadLocal.get();
        if (userInfoVo == null || userInfoVo.getUserId() == null) {
            // 没有登录
            return null;
        } else {
            // 已经登录，就从登录用户的在线购物车中把所有商品查出来
            List<CartItemVo> cartItemVoList = getCartItemVoList(userInfoVo.getUserId().toString());
            // 假如购物车为空，直接返回
            if (cartItemVoList == null || cartItemVoList.size() == 0) {
                return null;
            } else {
                List<CartItemVo> cartItemVoCheckedList = cartItemVoList.stream()
                        .filter(cartItemVo -> {
                            // 只获取已经选中的商品
                            return cartItemVo.getCheck();
                        }).map(cartItemVo -> {
                            // 这里还要用商品模块远程接口对商品价格进行一次更新
                            // 防止出现后台更改了商品价格的情况
                            BigDecimal price = productFeignService.updateCartPrice(cartItemVo.getSkuId());
                            cartItemVo.setPrice(price);
                            return cartItemVo;
                        }).collect(Collectors.toList());
                return cartItemVoCheckedList;
            }
        }
    }


    /**
     * 用于获取指定的cartKey购物车下的所有购物商品列表
     *
     * @param cartKey 购物车的key
     * @return 购物车商品列表
     */
    @Override
    public List<CartItemVo> getCartItemVoList(String cartKey) {
        // 绑定redis
        BoundHashOperations<String, Object, Object> cartKeyRedis =
                stringRedisTemplate.boundHashOps(CartConstant.CART_PREFIX + cartKey);
        // 获取这个reids下的所有购物商品value
        List<Object> cartItemList = cartKeyRedis.values();
        if (cartItemList != null && cartItemList.size() > 0) {
            // 把每一个redis商品转换成Vo类，做成List返回
            List<CartItemVo> cartItemVosList = cartItemList.stream().map((cartItem) -> {
                CartItemVo cartItemVo = JSON.parseObject(cartItem.toString(), CartItemVo.class);
                return cartItemVo;
            }).collect(Collectors.toList());
            return cartItemVosList;
        }
        return null;
    }


    /**
     * 用于删除指定的cartKey购物车下的所有购物商品列表
     *
     * @param cartKey 购物车的key
     */
    @Override
    public void clearCart(String cartKey) {
        // 直接将整个键下的购物车删除
        stringRedisTemplate.delete(CartConstant.CART_PREFIX + cartKey);
    }

}
